/*
 * Copyright 2010 Marcin Blazejewski
 *
 * This file is part of Nofate.
 *
 * Nofate is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Nofate is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Nofate. If not, see <http://www.gnu.org/licenses/>.
 *
 */


#include "map.hpp"
#include "font.h"
#include "thing.hpp"
#include "redeye/fov_point.h"

#include <list>
#include <math.h>
#include <string.h>
#include <iostream>

using namespace std;


static int          fov_width = 0;
static int          fov_height = 0;
static FovPoint    **fovmap = NULL;


/***********************************************************************
 * AreaMap
 **********************************************************************/

MapCell AreaMapRow::invalid;

AreaMapRow::AreaMapRow(int _size, MapCell *_r) : size(_size), row(_r) {
}

MapCell& AreaMapRow::operator[](int i) {
    if ((i >= 0) && (i < size)) return row[i];
    return invalid;
}

AreaMapRow AreaMap::invalid(0, NULL);

AreaMap::AreaMap(int w, int h) {
    int x;
    
    width = w;
    height = h;
    
    cells = new MapCell*[w];
    for (x = 0; x < w; x++) {
        cells[x] = new MapCell[h];
        memset(cells[x], 0, sizeof(MapCell) * h);
    }
    
    memset(lights, 0, sizeof(Light*) * 50);
}

AreaMap::~AreaMap() {
    int x, i;
    list<Thing*>::iterator ti;
    list<Thing*>::iterator ri; 
    list<Action*>::iterator ai;

    if (cells) {
        for (x = 0; x < width; x++) {
            if (cells[x]) delete [] cells[x];
        }
        delete [] cells;
    }
    
    for (i=0; i<50; i++) {
        if (lights[i]) delete [] lights[i];
    }

    for (ti = things.begin(); ti != things.end(); ti++) {
        delete *ti;
    }

    for (ri = terrains.begin(); ri != terrains.end(); ri++) {
        delete *ri;
    }

    for (ai = events.begin(); ai != events.end(); ai++) {
        delete *ai;
    }
}

AreaMapRow AreaMap::operator[](int i) { 
    if ((i >= 0) && (i < width)) return AreaMapRow(height, cells[i]); 
    return invalid;
}

void AreaMap::add_thing(Thing *t) {
    MapCell *cell = &cells[t->x][t->y];

    cell->alpha += t->alpha;
    t->area = this;

    if (t->type == TH_TYPE_CREATURE) {
        things.push_back(t);

        Creature *cre = (Creature*)t;
        if (cre->mv) free(cre->mv);
        cre->mv = new MapView(*this);
    }
    else if (t->type == TH_TYPE_TERRAIN) {
        terrains.push_back(t);
        terrains.unique();
        cell->terrain = t;
        Aterrain *ter = (Aterrain*)t->attributes["terrain"];
        cell->bg = ter->bg;
    } 
    else {
        things.push_back(t);
    }
}

void AreaMap::remove_thing(Thing *t) {
    MapCell *cell = &cells[t->x][t->y];
    cell->alpha -= t->alpha;
    
    things.remove(t);
}

void AreaMap::move_thing(Thing *t, int x, int y) {
    if ((x < 0) || (y < 0) || (x >= width) || (y >= height))
        return;

    MapCell *cell = &cells[t->x][t->y];
    cell->alpha -= t->alpha;

    cell = &cells[x][y];
    cell->alpha += t->alpha;
    t->x = x;
    t->y = y;
}

void AreaMap::ter_set(int x, int y, Thing *t, int bg) {
    cells[x][y].alpha = t->alpha;
    cells[x][y].terrain = t;
    
    if (bg == -1) {
        Aterrain *ter = (Aterrain*)t->attributes["terrain"];
        if (ter) cells[x][y].bg = ter->bg;
    }
    else if (bg != -2) {
        cells[x][y].bg = bg;
    }
}

void AreaMap::ter_fill(Thing *t) {
    int x, y;
    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            Aterrain *ter = (Aterrain*)t->attributes["terrain"];
            cells[x][y].alpha = t->alpha;
            cells[x][y].terrain = t;
            if (ter) cells[x][y].bg = ter->bg;
        }
    }
}

void AreaMap::set_ambient(char light) {
    int x, y;
    int diff = light - ambient_light;
    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            cells[x][y].light += diff;
        }
    }
    ambient_light = light;
}

int AreaMap::light_add(int x, int y, int value, int cutoff) {
    if ((x < 0) || (x >= width) || (y < 0) || (y >= height)) return NULL;
    if ((value <= 0) || (cutoff <= 0)) return NULL;

    Light *l = (Light*)malloc(sizeof(Light));
    l->x = x;
    l->y = y;    
    l->value = value;
    l->cutoff = cutoff;
    
    int i;
    for (i = 0; i < 50; i++) {
        if (lights[i] == NULL) {
            lights[i] = l;
            break;
        }
    }
    
    if (i >= 50) {
        free(l);
        i = -1;
    }
    
    return i;
}

int AreaMap::light_remove(int l) {
    if ((l < 0) || (l >= 50)) return -1;
    if (lights[l] == NULL) return -1;
    
    free(lights[l]);
    lights[l] = NULL;
    
    return -4;
}

void AreaMap::relight() {
    int x, y, i;
    
    for (x = 0; x < width; x++) {
        for (y = 0; y < height; y++) {
            cells[x][y].light = ambient_light;
        }
    }
    
    for (i=0; i<50; i++) {
        if (lights[i] != NULL)
            fov_light(this, lights[i]);
    }
}

int AreaMap::add_action(Action *a, Creature *cre) {
    int rc = 0;

    if (rc == 0) {
        a->area = this;
        a->cre = cre;
        rc = a->start();
    }

    if (rc == 0) {
        events.push_back(a);
        cre->busy++;
    }

    return rc;
}

int AreaMap::tick() {
    list<Action*>::iterator i, j;
    Action *a;

    for (i = events.begin(); i != events.end(); i=j) {
        // remember next element - current one might be destroyed
        // so i->next won't be there any more at the end of loop iter
        j = i;
        j++;

        // tick event
        a = *i;
        a->tick();

        // remove from action list if done
        if (a->timer <= 0) {
            a->cre->busy--;
            free(a);

            events.remove(*i);
        }
    }

    return 0;
}

/***********************************************************************
 * MapView
 **********************************************************************/

/*
 * viewport width,height are used in re_draw_view
 */
MapView::MapView(AreaMap &_source) : source(_source) {
    int x;
    
    fovmap = new char*[_source.width];
    for (x = 0; x < _source.width; x++) {
        fovmap[x] = new char[_source.height];
        memset(fovmap[x], 0, sizeof(char) * _source.height);
    }
}

MapView::~MapView() {
    for (int x = 0; x < source.width; x++) {
        delete [] fovmap[x];
    }

    delete [] fovmap;
}


/***********************************************************************
 * FOV
 **********************************************************************/

int fov_init(int width, int height)
{
    if (fovmap) fov_end();
    
    fovmap = (FovPoint**)malloc(sizeof(FovPoint*) * width);
    memset(fovmap, 0, sizeof(FovPoint*) * width);
    int x;
    for (x=0; x < width; x++)
    {
        fovmap[x] = (FovPoint*)malloc(sizeof(FovPoint) * height);
        memset(fovmap[x], 0, sizeof(FovPoint) * height);
    }
    
    fov_width = width;
    fov_height = height;
    
    return 0;
}

void fov_end()
{
    if (fovmap)
    {
        int x;
        for (x=0; x < fov_width; x++)
        {
            if (fovmap[x]) free(fovmap[x]);
            fovmap[x] = NULL;
        }
        free(fovmap);
        fovmap = NULL;
    }
}

void fov_reset(MapView *mv)
{
    int x, y;
    for (x = 0; x < fov_width; x++)
    {
        for (y = 0; y < fov_height; y++)
        {
            fovmap[x][y].state = FOV_NONE;
            if ((mv->fovmap[x][y] == FOV_OBSTACLE) || (mv->fovmap[x][y] == FOV_LIT))
            {
                mv->fovmap[x][y] = FOV_FOG;
            }
        }
    }
}

void fov_map(MapView *mv, int ox, int oy, int maxDistance)
{
    if (mv == NULL) return;
    if (!fovmap) fov_init(mv->source.width, mv->source.height);
    
    int ax, ay;
    std::list<FovPoint*> active;
    
    fov_reset(mv);
    
    int maxx = mv->source.width;
    int maxy = mv->source.height;
    int max2 = maxDistance * maxDistance;
    
    FovPoint p0;
    fp_reset(&p0, 0, 0);
    fp_expand(&p0, active, fovmap, ox, oy, maxx, maxy);
    
    while (active.size() > 0)
    {
        FovPoint *p = active.front();
        active.pop_front();
        
        ax = p->x + ox;
        ay = p->y + oy;
        
        int a = mv->source.cells[ax][ay].alpha;
        
        int distance2 = (p->x * p->x + p->y * p->y);
        if (distance2 >= max2) continue;
        
        fp_process(p, a);
        
        //printf("[dd] (%d,%d) = [%d,%d] xin=(%X) yin=(%X) a=%d, active=%d, state=%d \n", p->x, p->y, ax, ay, p->xInput, p->yInput, a, p->active, p->state);
        
        if (p->active)
        {
            mv->fovmap[ax][ay] = fovmap[ax][ay].state;
            fp_expand(p, active, fovmap, ox, oy, maxx, maxy);
        }
    }
}


void fov_light(AreaMap *am, Light *light)
{
    if (am == NULL) return;
    if (!fovmap) fov_init(am->width, am->height);
    
    int ax, ay;
    std::list<FovPoint*> active;
    
    int x, y;
    for (x = 0; x < fov_width; x++)
    {
        for (y = 0; y < fov_height; y++)
        {
            fovmap[x][y].state = FOV_NONE;
        }
    }
    //light->lit_cells.clear();
    
    int maxx = am->width;
    int maxy = am->height;
    int max2 = light->cutoff * light->cutoff;
    
    am->cells[light->x][light->y].light += light->value;
    FovPoint p0;
    fp_reset(&p0, 0, 0);
    fp_expand(&p0, active, fovmap, light->x, light->y, maxx, maxy);
    
    while (active.size() > 0)
    {
        FovPoint *p = active.front();
        active.pop_front();
        
        ax = p->x + light->x;
        ay = p->y + light->y;
        
        int a = am->cells[ax][ay].alpha;
        
        int distance2 = (p->x * p->x + p->y * p->y);
        if (distance2 >= max2) continue;
        
        fp_process(p, a);
        
        //printf("[dd] (%d,%d) = [%d,%d] xin=(%X) yin=(%X) a=%d, active=%d, state=%d \n", p->x, p->y, ax, ay, p->xInput, p->yInput, a, p->active, p->state);
        
        if (p->active)
        {
            int l = am->cells[ax][ay].light;
            l += (light->value / sqrt(distance2));
            if (l > 100) l = 100;
            am->cells[ax][ay].light = l;            
            
            fp_expand(p, active, fovmap, light->x, light->y, maxx, maxy);
        }
    }
    
}
